﻿using DataTransfer.Core.Providers;
using DataTransfer.SDK;
using DataTransfer.SDK.Events;
using DataTransfer.SDK.Models;
using MiniFox.Components;
using MiniFox.Platform.Applications.Auth;
using MiniFox.Platform.File.Models;
using MiniFox.Platform.File.Services;
using MiniFox.Resources;
using MiniFox.Utility;
using NPOI.HSSF.UserModel;
using NPOI.HSSF.Util;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System.Data;

namespace DataTransfer.Core.Services
{
    /// <summary>
    /// 
    /// </summary>
    [AutoResolve(COMPONENT_NAME)]
    public class ExcelExportEngine : ExportEngine, IExcelExportEventListener
    {
        public const string COMPONENT_NAME = "excel";
        internal const int MAX_97_ROWCOUNT = 65536;
        internal const int MAX_07_ROWCOUNT = 1048576;

        public event EventHandler<ExportExcelHeaderCreatedEventArgs> HeaderExport;
        public event EventHandler<ExportExcelEventArgs> BeforeExport;
        public event EventHandler<ExportExcelEventArgs> AfterExport;
        public event EventHandler<ExportExcelSheetEventArgs> SheetExporting;
        public event EventHandler<ExportExcelSheetEventArgs> SheetExported;
        public event EventHandler<ExportExcelRowEventArgs> RowExporting;
        public event EventHandler<ExportExcelRowEventArgs> RowExported;
        public event EventHandler<ExportExcelCellEventArgs> CellExporting;
        public event EventHandler<ExportExcelCellEventArgs> CellExported;

        bool _initialized;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="exporterName"></param>
        /// <param name="authStateManagement"></param>
        public ExcelExportEngine(string exporterName, IAuthStateManagement authStateManagement) : base(exporterName, authStateManagement)
        {
            _initialized = false;
        }
        /// <summary>
        /// 
        /// </summary>
        [AutoWired(COMPONENT_NAME)]
        protected override IExportConfigProvider Configuration { get; set; }
        /// <summary>
        /// 
        /// </summary>
        protected internal new IExcelExporter Exporter
        {
            get
            {
                var exporter = base.Exporter as IExcelExporter;
                return exporter;
            }
        }

        [InitInvoke]
        private void Initialize()
        {
            if (Exporter == null)
                return;
            if (_initialized)
                return;
            foreach (var module in Exporter.Modules)
            {
                var moduleType = module.Value;
                var type = typeof(IExcelExportModule);
                if (!type.IsAssignableFrom(moduleType))
                {
                    string msg = string.Format(CommonMessages.MSG_CommonTypeUnmatched, moduleType.FullName, type.FullName);
                    var exception = new InvalidOperationException(msg);
                    WriteWarnLog(exception.Message, exception);
                    continue;
                }
                var instance = Activator.CreateInstance(moduleType) as IExcelExportModule;

                instance.Init(this);
            }
            _initialized = true;
        }

        public int MaxRowCountPerSheet
        {
            get
            {
                int defaultMaxRowCount = Exporter.ExcelFormat == ExcelFormat.XLS ? MAX_97_ROWCOUNT : MAX_07_ROWCOUNT;
                return Exporter.MaxRowCountPerSheet <= 0 ? defaultMaxRowCount : Exporter.MaxRowCountPerSheet;
            }
        }

        public override string ExtensionName
        {
            get
            {
                string extName = ".xls";
                if (Exporter.ExcelFormat == ExcelFormat.XLSX)
                    extName = ".xlsx";
                return extName;
            }
        }

        #region 事件观察

        protected virtual void OnHeaderExport(ExportExcelHeaderCreatedEventArgs excelHeaderExportEventArgs)
        {
            HeaderExport?.Invoke(this, excelHeaderExportEventArgs);
        }

        protected virtual void OnSheetExporting(ExportExcelSheetEventArgs exportExcelSheetEventArgs)
        {
            SheetExporting?.Invoke(this, exportExcelSheetEventArgs);
        }

        protected virtual void OnSheetExported(ExportExcelSheetEventArgs exportExcelSheetEventArgs)
        {
            SheetExported?.Invoke(this, exportExcelSheetEventArgs);
        }

        protected virtual void OnRowExporting(ExportExcelRowEventArgs rowExportedEventArgs)
        {
            RowExporting?.Invoke(this, rowExportedEventArgs);
        }

        protected virtual void OnRowExported(ExportExcelRowEventArgs rowExportedEventArgs)
        {
            RowExported?.Invoke(this, rowExportedEventArgs);
        }

        protected virtual void OnCellExporting(ExportExcelCellEventArgs cellExportedEventArgs)
        {
            CellExporting?.Invoke(this, cellExportedEventArgs);
        }

        protected virtual void OnCellExported(ExportExcelCellEventArgs cellExportedEventArgs)
        {
            CellExported?.Invoke(this, cellExportedEventArgs);
        }

        protected virtual void OnBeforeExport(ExportExcelEventArgs exportEventArgs)
        {
            BeforeExport?.Invoke(this, exportEventArgs);
        }

        protected virtual void OnAfterExport(ExportExcelEventArgs exportEventArgs)
        {
            AfterExport?.Invoke(this, exportEventArgs);
        }

        #endregion

        private IWorkbook CreateHSSFWorkbook()
        {
            return new HSSFWorkbook();
        }
        private IWorkbook CreateXSSFWorkbook()
        {
            return new XSSFWorkbook();
        }
        protected virtual IWorkbook CreateWorkBook()
        {
            IWorkbook workbook;
            switch (Exporter.ExcelFormat)
            {
                case ExcelFormat.XLSX:
                    workbook = CreateXSSFWorkbook();
                    break;
                case ExcelFormat.XLS:
                default:
                    workbook = CreateHSSFWorkbook();
                    break;
            }

            return workbook;
        }

        IDictionary<string, IDictionary<string, ICellStyle>> _sheetCellStyles;
        protected IDictionary<string, IDictionary<string, ICellStyle>> SheetCellStyles
        {
            get
            {
                return _sheetCellStyles;
            }
        }


        private IDictionary<string, IDictionary<string, ICellStyle>> CreateStyleDictionary(IWorkbook workbook)
        {
            _sheetCellStyles = new Dictionary<string, IDictionary<string, ICellStyle>>();
            Exporter.Sheets.ForEach(excelExportSheet =>
            {
                var styles = CreateSheetStyle(workbook, excelExportSheet);
                _sheetCellStyles.Add(excelExportSheet.SheetName, styles);
            });
            return _sheetCellStyles;
        }

        private IDictionary<string, ICellStyle> CreateSheetStyle(IWorkbook workbook, IExcelExportSheet excelExportSheet)
        {
            var styles = new Dictionary<string, ICellStyle>();
            excelExportSheet.Fields.ForEach(field =>
            {
                var cellStyle = CreateCellStyle(workbook, field, excelExportSheet.Bordered);
                styles.Add(field.FieldName, cellStyle);
            });
            return styles;
        }

        private ICellStyle CreateCellStyle(IWorkbook workbook, IExcelExportSheetField field, bool borderd)
        {
            var cellStyle = workbook.CreateCellStyle();
            if (borderd)
            {
                cellStyle.BorderLeft = BorderStyle.Thin;
                cellStyle.BorderRight = BorderStyle.Thin;
                cellStyle.BorderBottom = BorderStyle.Thin;
                cellStyle.BorderTop = BorderStyle.Thin;
            }

            if (!string.IsNullOrEmpty(field.Format))
            {
                var dataFormat = workbook.CreateDataFormat();
                cellStyle.DataFormat = dataFormat.GetFormat(field.Format);
            }

            IFont font = workbook.CreateFont();
            font.FontHeightInPoints = field.FontSize;
            font.IsBold = false;
            cellStyle.SetFont(font);

            return cellStyle;
        }

        private ICellStyle CreateHeadStyle(IWorkbook workbook, IExcelExportSheetField field, bool borderd)
        {
            var headerStyle = workbook.CreateCellStyle();
            if (borderd)
            {
                headerStyle.BorderLeft = BorderStyle.Thin;
                headerStyle.BorderRight = BorderStyle.Thin;
                headerStyle.BorderBottom = BorderStyle.Thin;
                headerStyle.BorderTop = BorderStyle.Thin;
            }

            headerStyle.FillForegroundColor = HSSFColor.Indigo.Index;
            headerStyle.FillPattern = FillPattern.SolidForeground;

            IFont font = workbook.CreateFont();

            font.Color = HSSFColor.White.Index;
            font.IsBold = true;
            font.FontHeightInPoints = field.HeaderFontSize;
            headerStyle.SetFont(font);

            return headerStyle;
        }

        protected virtual int CreateTableHeader(ISheet sheet, IExcelExportSheet excelExportSheet)
        {
            int headerRowCount = excelExportSheet.HeaderRowIndex;
            IRow headRow = sheet.CreateRow(excelExportSheet.HeaderRowIndex);
            headRow.Height = excelExportSheet.HeaderHeight;
            var workbook = sheet.Workbook;
            int i = excelExportSheet.SkipColumnCount;
            excelExportSheet.Fields.ForEach(field =>
            {
                ICell headCell = headRow.CreateCell(i++, CellType.String);
                if (field.Width > 0)
                    sheet.SetColumnWidth(headCell.ColumnIndex, field.Width);
                headCell.SetCellValue(string.IsNullOrEmpty(field.DisplayName) ? field.FieldName : field.DisplayName);

                headCell.CellStyle = CreateHeadStyle(workbook, field, excelExportSheet.Bordered);
            });
            headerRowCount++;

            var e = new ExportExcelHeaderCreatedEventArgs(excelExportSheet, headRow);
            OnHeaderExport(e);

            return headerRowCount;
        }
        public virtual IWorkbook ExportDataToExcel(DataSet dataSet)
        {
            IWorkbook workbook = CreateWorkBook();
            ExportDataToWorkbook(workbook, dataSet);
            return workbook;
        }

        public void ExportDataToWorkbook(IWorkbook workbook, DataSet dataSet)
        {
            CreateStyleDictionary(workbook);
            var e = new ExportExcelEventArgs(Exporter, workbook, dataSet);
            OnBeforeExport(e);
            Exporter.Sheets.ForEach(sheet =>
            {
                var table = dataSet.Tables[sheet.SheetName];
                ExportDataToWorksheet(workbook, sheet, table);
            });
            OnAfterExport(e);
        }

        protected virtual void ExportDataToWorksheet(IWorkbook workbook, IExcelExportSheet excelExportSheet, DataTable dataTable)
        {
            if (dataTable.Rows.Count >= MaxRowCountPerSheet) //总行数大于最大支持行数时，自动分表
            {
                int sheetIndex = 0;
                int rowCount = 0;

                while (rowCount < dataTable.Rows.Count)//循环分表
                {
                    string sheetName = $"{dataTable.TableName}_P{sheetIndex++}";
                    //创建sheet
                    ISheet sheet = workbook.CreateSheet(sheetName);
                    int startRowIndex = rowCount;
                    rowCount += ExportDataToWorkSheet(sheet, excelExportSheet, dataTable, startRowIndex);
                }
            }
            else
            {
                //创建sheet
                ISheet sheet = workbook.CreateSheet(dataTable.TableName);
                ExportDataToWorkSheet(sheet, excelExportSheet, dataTable);
            }
        }
        public int ExportDataToWorkSheet(ISheet sheet, IExcelExportSheet excelExportSheet, DataTable dataTable)
        {
            return ExportDataToWorkSheet(sheet, excelExportSheet, dataTable, 0);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sheet"></param>
        /// <param name="dataTable"></param>
        /// <param name="startRowIndex">起始导入行，指定从dataTable的哪一行开始读取</param>
        /// <returns></returns>
        protected virtual int ExportDataToWorkSheet(ISheet sheet, IExcelExportSheet excelExportSheet, DataTable dataTable, int startRowIndex)
        {
            var e = new ExportExcelSheetEventArgs(excelExportSheet, sheet, dataTable);
            OnSheetExporting(e);
            int headerRowCount = CreateTableHeader(sheet, excelExportSheet) + excelExportSheet.SkipRowCount;//创建表头，并返回表头占sheet总行数
            int maxOffset = MaxRowCountPerSheet - headerRowCount; //最大读取偏移量

            int rowIndex = startRowIndex;//data table起始行索引
            int rowCount = 0;
            for (int i = headerRowCount; i < maxOffset; i++)
            {
                if (rowIndex >= dataTable.Rows.Count)
                    break;
                DataRow dataRow = dataTable.Rows[rowIndex++];
                IRow row = sheet.CreateRow(i);
                row.Height = excelExportSheet.RowHeight;
                ExportRowToWorkSheet(row, dataRow, excelExportSheet);
                rowCount++;
            }
            OnSheetExported(e);
            return rowCount;
        }

        private void ExportRowToWorkSheet(IRow row, DataRow dataRow, IExcelExportSheet excelExportSheet)
        {
            var e = new ExportExcelRowEventArgs(excelExportSheet, row, dataRow);
            OnRowExporting(e);
            ExportRow(row, dataRow, excelExportSheet);
            OnRowExported(e);
        }

        protected virtual void ExportRow(IRow row, DataRow dataRow, IExcelExportSheet excelExportSheet)
        {
            int i = excelExportSheet.SkipColumnCount;
            excelExportSheet.Fields.ForEach(field =>
            {
                ICell cell = row.CreateCell(i++);
                cell.CellStyle = SheetCellStyles[excelExportSheet.SheetName][field.FieldName];
                ExportCellToWorkSheet(cell, dataRow, field);
            });
        }

        private void ExportCellToWorkSheet(ICell cell, DataRow dataRow, IExcelExportSheetField field)
        {
            var e = new ExportExcelCellEventArgs(field, cell, dataRow);
            OnCellExporting(e);
            ExportCell(cell, dataRow, field);
            OnCellExported(e);
        }

        protected virtual void ExportCell(ICell cell, DataRow dataRow, IExcelExportSheetField field)
        {
            object dataVal = dataRow[field.FieldName];
            if (dataVal == null && dataVal == DBNull.Value)
            {
                cell.SetCellType(CellType.Blank);
            }
            else
            {
                ExcelTransferUtility.SetCellValue(cell, dataVal, field.Type);
            }
        }

        protected override byte[] ExportResultToBytes(object result)
        {
            var dataSet = result as DataSet;
            using (var stream = new MemoryStream())
            {
                using (IWorkbook workbook = ExportDataToExcel(dataSet))
                {
                    workbook.Write(stream);
                    return stream.ToArray();
                }
            }
        }

        protected override FileData CreateUniqueFile(string fileName)
        {
            var fileData = base.CreateUniqueFile(fileName);
            switch (Exporter.ExcelFormat)
            {
                case ExcelFormat.XLSX:
                    fileData.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                    break;
                case ExcelFormat.XLS:
                    fileData.ContentType = "application/vnd.ms-excel";
                    break;
                default:
                    fileData.ContentType = MimeTypes.Stream;
                    break;
            }
            return fileData;
        }
    }
}